﻿using Abp.Dependency;
using Abp.Runtime.Session;
using Abp.Timing;
using Abp.UI;
using DotNetCore.CAP;
using Hangfire;
using Hicap.QRCodeWarehouse.Core.Common;
using Hicap.QRCodeWarehouse.Core.EFCore;
using Hicap.QRCodeWarehouse.Core.Models.DbSet;
using Hicap.QRCodeWarehouse.Core.Models.Other;
using Hicap.QRCodeWarehouse.Core.Models.Transfer;
using Hicap.QRCodeWarehouse.Core.Notify;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Newtonsoft.Json;
using Serilog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace Hicap.QRCodeWarehouse.Core.Managers
{
    public class GenerationTaskManager : ManagerBase<GenerationTask>, IGenerationTaskManager
    {
        private readonly NotifyHub _notifyHub;
        private readonly IAbpSession _session;
        private readonly ICapPublisher _capBus;
        private static readonly object lockObject = new object();

        public GenerationTaskManager(IUnitOfWork unitOfWork, IAbpSession session, ICapPublisher capBus,
            NotifyHub notifyHub, IAbpSession session1) : base(unitOfWork)
        {
            _capBus = capBus;
            _notifyHub = notifyHub;
            _session = session1;

            ForeignKeys.Add(x => x.Product);

            QueryCondition.Add(x => x.IsDeleted == false);

            int? companyId = session.GetCompanyId();
            if (companyId.HasValue) QueryCondition.Add(x => x.SubordinateCompanyId == companyId.Value);

            CreateActions.Add(x => x.CreatorUserId = session.UserId);
            CreateActions.Add(x => x.CreationTime = Clock.Now);
            CreateActions.Add(x => x.Status = GenerationStatus.Stop);
            UpdateActions.Add(x => x.LastModificationTime = Clock.Now);
            UpdateActions.Add(x => x.LastModifierUserId = session.UserId);
            DeleteActions.Add(x => x.DeletionTime = Clock.Now);
            DeleteActions.Add(x => x.DeleterUserId = session.UserId);
            DeleteActions.Add(x => x.IsDeleted = true);

            CreateCheckActions.Add(x =>
            {
                if (x.Id == 0) return true;
                Log.Warning($"GenerationTask CreateCheck Fail : {JsonConvert.SerializeObject(x)}");
                return false;
            });
            UpdateCheckActions.Add(x =>
            {
                if (x.Id > 0 && x.IsDeleted == false) return true;
                Log.Warning($"GenerationTask UpdateCheck Fail : {JsonConvert.SerializeObject(x)}");
                return false;
            });
            DeleteCheckActions.Add(x =>
            {
                if (x.IsDeleted == false) return true;
                Log.Warning($"GenerationTask DeleteCheck Fail : {JsonConvert.SerializeObject(x)}");
                return false;
            });
        }

        public override IQueryable<GenerationTask> GetQuery(Expression<Func<GenerationTask, bool>> predicate)
        {
            return base.GetQuery(predicate)
                .Include(x => x.Product).ThenInclude(x => x.InnerGenerationRule)
                .Include(x => x.Product).ThenInclude(x => x.OuterGenerationRule);
        }

        public async Task PutTimedTask(GenerationTask input)
        {
            if (input.Type == GenerationTaskType.Manual)
                throw new UserFriendlyException("Manual input can not be time trggierd");

            if (await InsertOrUpdate(input) > 0)
            {
                if (input.IsActive)
                {
                    AddOrUpdateRecurringJob(input);

                    return;
                }

                RecurringJob.RemoveIfExists("task_" + input.Id);
            }
        }

        private void AddOrUpdateRecurringJob(GenerationTask input)
        {
            RecurringJob.AddOrUpdate("task_" + input.Id, () => TimedPublishMessage(input), input.CronExpression,
                TimeZoneInfo.Local);

            return;
        }

        public void TimedPublishMessage(GenerationTask input)
        {
            Task.Run(async () =>
            {
                TaskCommandDto output = GetTaskCommandDto(input.Id, out GenerationTask task);
                task.Status = GenerationStatus.Running;
                task.TriggeredTime = Clock.Now;

                if (await Update(task, new List<Action<GenerationTask>>()
                {
                    x => x.LastModifierUserId = task.CreatorUserId,
                    x => x.LastModificationTime = Clock.Now
                }) > 0)
                {
                    await _capBus.PublishAsync(MessageQueueChanelConst.CodeGenServiceGenAction, output,
                        MessageQueueChanelConst.CodeGenServiceGenAction + ".End");
                }
            });
        }

        public async Task SwitchActive(int taskId, bool isActive)
        {
            GenerationTask task = await GetQuery(x => x.Id == taskId).FirstOrDefaultAsync();

            if (task != null)
            {
                task.IsActive = isActive;

                if (await Update(task) > 0)
                {
                    await Task.Run(() =>
                    {
                        if (!isActive)
                            RecurringJob.RemoveIfExists("task_" + taskId);
                        else
                            AddOrUpdateRecurringJob(task);
                    });
                }
            }
        }

        public async Task<GenerationTask> StartTask(int taskId)
        {
            TaskCommandDto output = GetTaskCommandDto(taskId, out GenerationTask task);

            if (task == null) throw new UserFriendlyException("No found task");
            if (task.Type == GenerationTaskType.Timed) throw new UserFriendlyException("Auto input can not be started");
            Tuple<GenerationStatus, DateTime?> orginValues =
                new Tuple<GenerationStatus, DateTime?>(task.Status, task.TriggeredTime);

            task.Status = GenerationStatus.Running;
            task.TriggeredTime = Clock.Now;

            if (await Update(task) > 0)
            {
                await _capBus.PublishAsync(MessageQueueChanelConst.CodeGenServiceGenAction, output,
                    MessageQueueChanelConst.CodeGenServiceGenAction + ".End");
            }
            else
            {
                task.Status = GenerationStatus.Exception;
                task.TriggeredTime = orginValues.Item2;
            }

            return task;
        }

        [CapSubscribe(MessageQueueChanelConst.CodeGenServiceGenAction + ".End", Group = SysConsts.SysName)]
        public void FinalFeedBackMessage(ActionResult result)
        {
            Task.Run(async () =>
            {
                await _notifyHub.SendToGroup(NotificationMethod.GenerationTaskFeedBack + ".End",
                    JsonConvert.SerializeObject(result));
            });

            if (result.IsSuccessed)
                Log.Information($"FeedBack Finished:{MessageQueueChanelConst.CodeGenServiceGenAction}--{result}");
            else
                Log.Error($"FeedBack have a Error:{MessageQueueChanelConst.CodeGenServiceGenAction}--{result}");
        }

        private TaskCommandDto GetTaskCommandDto(int taskId, out GenerationTask task)
        {
            IIncludableQueryable<GenerationTask, GenerationRule> query = GetQuery(x => x.Id == taskId)
                .Include(x => x.Product).ThenInclude(x => x.InnerGenerationRule)
                .Include(x => x.Product).ThenInclude(x => x.OuterGenerationRule);

            task = query.FirstOrDefault();

            GenerationRule rule = task.CodeType == GenerationRuleType.Inner
                ? task.Product.InnerGenerationRule
                : task.Product.OuterGenerationRule;

            string urlPrefix = task.CodeType == GenerationRuleType.Inner
                ? task.Product.InnerPrefixUrl
                : task.Product.OuterPrefixUrl;

            bool isOnlyNumberCode = task.CodeType == GenerationRuleType.Inner
                ? task.Product.InnerGenerationRule.IsOnlyNumberCode
                : task.Product.OuterGenerationRule.IsOnlyNumberCode;

            TaskCommandDto output = new TaskCommandDto()
            {
                Amount = task.Amount,
                TaskId = task.Id,
                AutoIncrementLength = rule.AutoIncrementLength,
                GenerationRuleType = task.CodeType,
                PrefixUrl = urlPrefix,
                ProductId = task.ProductId,
                ProductCode = task.Product.ProductCode,
                RandomLength = rule.RandomLength,
                SubordinateCompanyId = task.SubordinateCompanyId,
                IsOnlyNumberCode = isOnlyNumberCode,
                UserId = Convert.ToInt32(task.CreatorUserId)
            };

            return output;
        }

        [CapSubscribe(MessageQueueChanelConst.CodeGenServiceFeedBack, Group = SysConsts.SysName)]
        public async Task<ActionResult> DispatchCodeGenFeedback(TaskFeedbackDto data)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            {
                try
                {
                    if (data.ProgressPct == 1)
                    {
                        await CompleteTask(data);
                    }

                    await _notifyHub.SendToGroup(NotificationMethod.GenerationTaskFeedBack,
                        JsonConvert.SerializeObject(data));

                    return new ActionResult(MessageQueueChanelConst.CodeGenServiceFeedBack, true,
                        watch.ElapsedMilliseconds, Clock.Now, data);
                }
                catch (Exception e)
                {
                    await CompleteTask(data, false);

                    return new ActionResult(MessageQueueChanelConst.CodeGenServiceFeedBack, false,
                        watch.ElapsedMilliseconds,
                        Clock.Now, data, e);
                }
            }
        }

        private async Task CompleteTask(TaskFeedbackDto data, bool isSuccessed = true)
        {
            using (IDisposableDependencyObjectWrapper<IUnitOfWork> resolveObject =
                IocManager.Instance.ResolveAsDisposable<IUnitOfWork>())
            {
                using (IUnitOfWork uow = resolveObject.Object)
                {
                    IRepository<GenerationTask> repository = uow.GetRepository<GenerationTask>();
                    GenerationTask entity =
                        await repository.GetIncludingQuery(x => x.Product)
                            .FirstOrDefaultAsync(x => x.Id == data.TaskId);

                    if (entity != null && entity.Status != GenerationStatus.Complete &&
                        entity.Status != GenerationStatus.Exception)
                    {
                        if (isSuccessed)
                        {
                            entity.Status = GenerationStatus.Complete;

                            if (entity.CodeType == GenerationRuleType.Inner)
                                entity.Product.TotoalInnerCount += data.TotalAmount;
                            else
                                entity.Product.TotoalOuterCount += data.TotalAmount;
                        }
                        else
                        {
                            entity.Status = GenerationStatus.Exception;
                        }

                        entity.TriggeredTime = Clock.Now;
                        entity.LastModifierUserId = data.UserId;
                        entity.LastModificationTime = Clock.Now;

                        await repository.UpdateAsync(entity);
                        await uow.SaveChangesAsync(true);
                    }
                }
            }
        }
    }
}